#cmake_minimum_required(VERSION 3.20)
#set(CMAKE_CXX_STANDARD 17)
#message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")

# 项目名称
set(TARGET_NAME Algebra)
set(PROJECT_NAME Algebra)

# project(Algebra LANGUAGES CXX)

# 引入宏与函数
include (GNUInstallDirs)

# Automatically add the current source and build directories to the include path.
# see https://cmake.org/cmake/help/v3.31/variable/CMAKE_INCLUDE_CURRENT_DIR.html
SET(CMAKE_INCLUDE_CURRENT_DIR ON)

# # Automatically add the current source and build directories to the
# # INTERFACE_INCLUDE_DIRECTORIES target property.
# SET(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
if(MINGW)
  # 编译参数 先不考虑MSVC
  set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb -Wa,-mbig-obj")
  set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall -Wa,-mbig-obj")
endif()

# # # verbose diagnostic messages for the 'Debug' configurations
# # # _DEBUG Defined as 1 when the /LDd, /MDd, or /MTd compiler option is set. Otherwise, undefined.
# # # see https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170#standard-predefined-identifier
# if (CMAKE_BUILD_TYPE MATCHES "[D|d]ebug")
# add_definitions(-DSG_DEBUG -DSG_VERBOSE)
# endif()

# ################################################################################
# Specify rules to build the target(s).
set(TARGET_INCLUDE_DIRS
  PRIVATE ${CMAKE_SOURCE_DIR}
  PRIVATE ${CMAKE_BINARY_DIR}
  PRIVATE ${CMAKE_SOURCE_DIR}/Utility/Algebra
  PRIVATE ${CMAKE_BINARY_DIR}/Utility/Algebra
)

set(TARGET_LIBRARY_DIRS
  PRIVATE ${CMAKE_INSTALL_PREFIX}/lib
)

set(TARGET_LIBRARIES)

set(TARGET_DEFINITIONS
  PRIVATE ALGEBRA_EXPORTS
  PRIVATE BASE_EXPORTS
)

# verbose diagnostic messages for the 'Debug' configurations
# _DEBUG Defined as 1 when the /LDd, /MDd, or /MTd compiler option is set. Otherwise, undefined.
# see https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170#standard-predefined-identifier
if(CMAKE_BUILD_TYPE MATCHES "[D|d]ebug")
  list(APPEND TARGET_DEFINITIONS "SG_DEBUG")
endif()

set(TARGET_HDRS
  BaseModule.h
  ArgvKeeper.h
  Object.h
  Factory.h
  Handle.h
  AlgebraModule.h
  AlgebraicTypes.h
  Error.h
  IndexArray.h
  IndexMap.h
  Sparsity.h
  Space.h
  Graph.h
  MathTools.h
  Matrix.h
  Point.h
  Tools.h
  Vector3D.h
  LinearSolver.h
  EigenSolver.h
  ElapsedTimer.h
  ODESolver.h
  ODESystem.h
  TimeSteppers.h
)

set(TARGET_SRCS
  BaseModule.cpp
  ArgvKeeper.cpp
  Object.cpp
  Factory.cpp
  Handle.cpp
  AlgebraModule.cpp
  Error.cpp
  Sparsity.cpp
  Graph.cpp
  MathTools.cpp
  Matrix.cpp
  Point.cpp
  Tools.cpp
  Vector3D.cpp
  LinearSolver.cpp
  EigenSolver.cpp
  ElapsedTimer.cpp
  ODESolver.cpp
  ODESystem.cpp
  TimeSteppers.cpp
)

# Generate configuration file in the build directory: Algebra/Config.h.
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/Config.h")

# configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/Config.h")
list(APPEND TARGET_HDRS ${CMAKE_CURRENT_BINARY_DIR}/Config.h)

#
# Generally, a dependency should be specified in a use of target_link_libraries()
# with the PRIVATE keyword if it is used by only the implementation of a library,
# and not in the header files. If a dependency is additionally used in the header
# files of a library (e.g. for class inheritance), then it should be specified as
# a PUBLIC dependency. A dependency which is not used by the implementation of a
# library, but only by its headers should be specified as an INTERFACE dependency.
#
# see https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#target-usage-requirements
#

# MPI
if(SG_USE_MPI)
  message(STATUS "Enable MPI parallel build")

  # # # Adds preprocessor definitions to the compiler command line for targets in the current
  # # # directory and below (whether added before or after this command is invoked)
  # # # see https://cmake.org/cmake/help/v3.12/command/add_compile_definitions.html
  # add_compile_definitions(SG_USE_MPI)
  list(APPEND TARGET_HDRS
    sgMpi.h
    MpiException.h
    MpiEnvironment.h
    MpiDataType.h
    MpiMessage.h
    MpiGroup.h
    MpiCommunicator.h
    MpiGraphCommunicator.h
  )
  list(APPEND TARGET_SRCS
    sgMpi.cpp
    MpiException.cpp
    MpiEnvironment.cpp
    MpiDataType.cpp
    MpiMessage.cpp
    MpiGroup.cpp
    MpiCommunicator.cpp
    MpiGraphCommunicator.cpp
  )

  # list(APPEND TARGET_DEFINITIONS SG_USE_MPI)

  # The module exposes the components C, CXX, MPICXX and Fortran.
  #
  # If no components are specified, this is true if MPI settings for all enabled languages were
  # detected. Note that the MPICXX component does not affect this variable.
  #
  # Additionally, the following IMPORTED targets are defined:
  # MPI::MPI_<lang>
  # Target for using MPI from <lang>.
  #
  # If the find procedure fails for a variable MPI_<lang>_WORKS, then the settings detected by
  # or passed to the module did not work and even a simple MPI test program failed to compile.
  # see https://cmake.org/cmake/help/latest/module/FindMPI.html
 
  set(MPI_CXX_SKIP_MPICXX ON)
    
  find_package(MPI QUIET REQUIRED)

  list(APPEND TARGET_LIBRARIES PRIVATE MPI::MPI_CXX)  
  
  if(MINGW)
    find_package(Parpack QUIET REQUIRED)
    list(APPEND TARGET_LIBRARIES PRIVATE Parpack::Parpack)
  endif()

  # PETSc
  # see https://petsc.org/release/faq/#can-i-use-cmake-to-build-my-own-project-that-depends-on-petsc
  # see https://petsc.org/release/manual/getting_started/#sec-writing-application-codes
  #if(SG_USE_PETSC)
  if (TRUE)
    message(STATUS "Build the linear solver using PETSc")

    # # # Adds preprocessor definitions to the compiler command line for targets in the current
    # # # directory and below (whether added before or after this command is invoked)
    # # # see https://cmake.org/cmake/help/v3.12/command/add_compile_definitions.html
    # add_compile_definitions(SG_USE_PETSC)
    list(APPEND TARGET_HDRS
      sgPetsc.h
      sgPetscSpace.h
    )
    list(APPEND TARGET_SRCS
      sgPetsc.cpp
      sgPetscSpace.cpp
      WrappedPetsc.cpp
    )

    # list(APPEND TARGET_DEFINITIONS SG_USE_PETSC)
    find_package(PETSc REQUIRED)
    message(STATUS "PETSc_VERSION: ${PETSc_VERSION}")
    message(STATUS "PETSc_PREFIX: ${PETSc_PREFIX}")

    list(APPEND TARGET_LIBRARIES PRIVATE PETSc::PETSc)

    # ##
    # ## The following variables may be set upon return.
    # ## see https://cmake.org/cmake/help/latest/module/FindPkgConfig.html
    # ##
    # ## However, for PETSc_LIBRARY_DIRS exported from the imported target,
    # ## it seems that the absolute path has been emitted. Thus, add the
    # ## library path munually.
    # ##
    list(APPEND TARGET_LIBRARY_DIRS ${PETSc_LIBRARY_DIRS})

    # MESSAGE(STATUS "PETSc_LIBRARIES = ${PETSc_LIBRARIES}")
    # MESSAGE(STATUS "PETSc_LINK_LIBRARIES=${PETSc_LINK_LIBRARIES}")
    # MESSAGE(STATUS "PETSc_LIBRARY_DIRS=${PETSc_LIBRARY_DIRS}")
    # MESSAGE(STATUS "PETSc_LDFLAGS = ${PETSc_LDFLAGS}")
    # MESSAGE(STATUS "PETSc_LDFLAGS_OTHER = ${PETSc_LDFLAGS_OTHER}")
    # MESSAGE(STATUS "PETSc_INCLUDE_DIRS = ${PETSc_INCLUDE_DIRS}")
    # MESSAGE(STATUS "PETSc_CFLAGS = ${PETSc_CFLAGS}")
    # MESSAGE(STATUS "PETSc_CFLAGS_OTHER = ${PETSc_CFLAGS_OTHER}")
    # MESSAGE(STATUS "PETSc_COMPILER = ${PETSc_COMPILER}")
  endif()

  # oneMKL
  if(SG_USE_MKL)
    message(STATUS "Build the linear solver using oneMKL")

    # # Adds preprocessor definitions to the compiler command line for targets in the current
    # # directory and below (whether added before or after this command is invoked)
    # # see https://cmake.org/cmake/help/v3.12/command/add_compile_definitions.html
    # add_compile_definitions(SG_USE_MKL)
    list(APPEND TARGET_HDRS sgCPardiso.h)
    list(APPEND TARGET_SRCS sgCPardiso.cpp)

    # list(APPEND TARGET_DEFINITIONS SG_USE_MKL)

    # ## list(APPEND TARGET_DEFINITIONS MKL_ILP64)

    # # see https://www.intel.com/content/www/us/en/docs/onemkl/developer-guide-windows/2024-2/cmake-config-for-onemkl.html
    # # Using Intel MKL with gfortran under Windows: undefined reference to `__security_cookie'
    # # see https://community.intel.com/t5/Intel-oneAPI-Math-Kernel-Library/Using-Intel-MKL-with-gfortran-under-Windows/m-p/1106062#M24113%3Fwapkw=__security_check_cookie
    find_package(MKL CONFIG REQUIRED)

    #
    # The comipler will complains the following issues,
    # /usr/bin/ld: /opt/intel/oneapi/mkl/2024.2/lib/libmkl_intel_lp64.so.2: underfiend refence to 'mkl_lapack_zla_gbrcond_x'
    # Actually, the corresponding implementation of the mentioned 'routine' is residing in 'mkl_core.so'.
    # It seems that the mkl link options should be propagated to other dependent projects.
    # The mkl dependencies are listed here,
    # - libmkl_intel_lp64.so
    # - libmkl_intel_thread.so
    # - libmkl_intel_thread.so
    # - libmkl_intel_thread.so
    #
    list(APPEND TARGET_LIBRARIES PRIVATE MKL::MKL)

    # list(APPEND TARGET_LIBRARIES PUBLIC MKL::MKL)
  endif()

  # PARPACK
  if(SG_USE_ARPACK)
    message(STATUS "Build the eigenvalue solver using PARPACK")

    list(APPEND TARGET_HDRS sgPArpack.h)
    list(APPEND TARGET_SRCS sgPArpack.cpp)

    find_package(arpackng REQUIRED CONFIG)
    if(arpackng_FOUND)  
      message(STATUS "arpackng_DIR: ${arpackng_DIR}")
      list(APPEND TARGET_LIBRARIES PRIVATE ARPACK::ARPACK)
    else()
      find_package(arpackng REQUIRED MODULE)
      if(arpackng_FOUND)
        message(STATUS "arpackng_DIR: ${arpackng_DIR}")
        list(APPEND TARGET_LIBRARIES PRIVATE ${arpackng_LIBRARIES})
        list(APPEND TARGET_INCLUDE_DIRS ${arpackng_INCLUDE_DIRS})
      else()
        message(FATAL_ERROR "Cannot find arpackng library, please install it or set the CMAKE_PREFIX_PATH to the directory containing arpackngConfig.cmake")
      endif()
    endif()

    list(APPEND TARGET_LIBRARIES PRIVATE PARPACK::PARPACK)
  endif()

else()
  list(APPEND TARGET_HDRS SparseBlas.h)
  list(APPEND TARGET_SRCS SparseBlas.cpp)

  # Eigen3
  list(APPEND TARGET_HDRS
    sgEigenSpace.h
    sgEigen.h
  )

  list(APPEND TARGET_SRCS
    sgEigenSpace.cpp
    sgEigen.cpp
  )

  # oneMKL
  if(SG_USE_MKL)
    message(STATUS "Build the linear solver using oneMKL")

    # # Adds preprocessor definitions to the compiler command line for targets in the current
    # # directory and below (whether added before or after this command is invoked)
    # # see https://cmake.org/cmake/help/v3.12/command/add_compile_definitions.html
    # add_compile_definitions(SG_USE_MKL)
    list(APPEND TARGET_HDRS sgPardiso.h)
    list(APPEND TARGET_SRCS sgPardiso.cpp)

    # list(APPEND TARGET_DEFINITIONS SG_USE_MKL)

    # ## list(APPEND TARGET_DEFINITIONS MKL_ILP64)

    # # see https://www.intel.com/content/www/us/en/docs/onemkl/developer-guide-windows/2024-2/cmake-config-for-onemkl.html
    # # Using Intel MKL with gfortran under Windows: undefined reference to `__security_cookie'
    # # see https://community.intel.com/t5/Intel-oneAPI-Math-Kernel-Library/Using-Intel-MKL-with-gfortran-under-Windows/m-p/1106062#M24113%3Fwapkw=__security_check_cookie
    find_package(MKL CONFIG REQUIRED)

    #
    # The comipler will complains the following issues,
    # /usr/bin/ld: /opt/intel/oneapi/mkl/2024.2/lib/libmkl_intel_lp64.so.2: underfiend refence to 'mkl_lapack_zla_gbrcond_x'
    # Actually, the corresponding implementation of the mentioned 'routine' is residing in 'mkl_core.so'.
    # It seems that the mkl link options should be propagated to other dependent projects.
    # The mkl dependencies are listed here,
    # - libmkl_intel_lp64.so
    # - libmkl_intel_thread.so
    # - libmkl_intel_thread.so
    # - libmkl_intel_thread.so
    #
    list(APPEND TARGET_LIBRARIES PRIVATE MKL::MKL)

    # list(APPEND TARGET_LIBRARIES PUBLIC MKL::MKL)
  endif()

  # ARPACK
  if(SG_USE_ARPACK)
    message(STATUS "Build the eigenvalue solver using ARPACK")

    # # # Adds preprocessor definitions to the compiler command line for targets in the current
    # # # directory and below (whether added before or after this command is invoked)
    # # # see https://cmake.org/cmake/help/v3.12/command/add_compile_definitions.html
    # add_compile_definitions(SG_USE_ARPACK)
    list(APPEND TARGET_HDRS sgArpack.h)
    list(APPEND TARGET_SRCS sgArpack.cpp)

    # list(APPEND TARGET_DEFINITIONS SG_USE_ARPACK)
    # list(APPEND TARGET_DEFINITIONS MKL_ILP64)
    find_package(arpackng REQUIRED CONFIG)
    if(arpackng_FOUND)  
      message(STATUS "arpackng_DIR: ${arpackng_DIR}")
      list(APPEND TARGET_LIBRARIES PRIVATE ARPACK::ARPACK)
    else()
      find_package(arpackng REQUIRED MODULE)
      if(arpackng_FOUND)
        message(STATUS "arpackng_DIR: ${arpackng_DIR}")
        list(APPEND TARGET_LIBRARIES PRIVATE ${arpackng_LIBRARIES})
        list(APPEND TARGET_INCLUDE_DIRS ${arpackng_INCLUDE_DIRS})
      else()
        message(FATAL_ERROR "Cannot find arpackng library, please install it or set the CMAKE_PREFIX_PATH to the directory containing arpackngConfig.cmake")
      endif()
    endif()
  endif()
endif(SG_USE_MPI)

# CUDA
if(SG_USE_CUDA)
  message(STATUS "Enable CUDA parallel build")
  find_package(CUDA)
endif(SG_USE_CUDA)

# # find_package(Eigen3 REQUIRED NO_MODULE)
# # list(APPEND TARGET_LIBRARIES PUBLIC Eigen3::Eigen)

# # SuperLU
# if (SG_USE_SUPERLU)

# message(STATUS "Build the linear solver using SuperLU")

# add_compile_definitions(SG_USE_SUPERLU)

# list(APPEND TARGET_HDRS sgSuperLU.h)
# list(APPEND TARGET_SRCS sgSuperLU.cpp)

# find_package(superlu REQUIRED)
# message(STATUS "superlu_DIR: ${superlu_DIR}")
# list(APPEND TARGET_LIBRARIES PRIVATE superlu::superlu)

# #add_compile_definitions(SG_USE_SUPERLU)
# list(APPEND TARGET_DEFINITIONS SG_USE_SUPERLU )

# endif (SG_USE_SUPERLU)

# 获取此项目源文件
set(SOURCES
  ${BASE_HDRS}
  ${BASE_SRCS}
  ${TARGET_HDRS}
  ${TARGET_SRCS}
)

add_library(${TARGET_NAME} SHARED ${SOURCES})

include_directories( ${TARGET_INCLUDE_DIRS})

# 设置头文件查找目录
target_include_directories(${TARGET_NAME} ${TARGET_INCLUDE_DIRS})


# 链接库
target_link_directories(${TARGET_NAME} ${TARGET_LIBRARY_DIRS})

# foreach(item IN LISTS TARGET_LIBRARIES)
# target_link_libraries(${TARGET_NAME} PRIVATE ${item})
# endforeach(item IN LISTS TARGET_LIBRARIES)
target_link_libraries(${TARGET_NAME} ${TARGET_LIBRARIES})

target_compile_definitions(${TARGET_NAME} ${TARGET_DEFINITIONS})

# # verbose diagnostic messages for the 'Debug' configurations
# # _DEBUG Defined as 1 when the /LDd, /MDd, or /MTd compiler option is set. Otherwise, undefined.
# # see https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170#standard-predefined-identifier
# if (CMAKE_BUILD_TYPE MATCHES "[D|d]ebug")
# target_compile_definitions(${TARGET_NAME} PRIVATE SG_DEBUG)
# endif()

# 导入第三方库
import_eigen3()

# ################################################################################
# Specify rules to run at install time.

# associates the installed target files with an export taget.
# see https://cmake.org/cmake/help/v3.7/command/install.html#installing-targets
# install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME})
install(
  TARGETS ${TARGET_NAME}
  EXPORT ${TARGET_NAME}
  ARCHIVE DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES DESTINATION include/Utility/${TARGET_NAME}
)

# 安装头文件
install(FILES ${TARGET_HDRS} DESTINATION include/Utility/${TARGET_NAME})

# generates and installs a CMake file containing code to import targets from
# the installation tree into another project.
# see https://cmake.org/cmake/help/v3.7/command/install.html#installing-targets
# install(EXPORT ${TARGET_NAME}
# DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake"
# CONFIGURATIONS Debug|Release
# )
# install(EXPORT ${TARGET_NAME}
# DESTINATION "lib/cmake"
# CONFIGURATIONS Debug|Release
# )

# 是否配置Test
if(SG_BUILD_UNIT_TEST)
  # enable_testing()
  add_subdirectory(test)
endif()

if(SG_BUILD_DOCUMENTATION)
  add_subdirectory(doc)
endif()

set_source_files_properties(sgArpack.cpp PROPERTIES COMPILE_FLAGS "-O0")

